`include "PRV564Config.v"
`include "PRV564Define.v"

// Author: Dingbang Liu
// Description: MD Pipelien executor

module md_execute(
// MD Pipeline global signals
    input wire clk,
    input wire arst,
    input wire stall,
    input wire flush,
// Input
    input wire[127:0] ex_data1,
    input wire[127:0] ex_data2,
    input wire ex_div, // 0 for mul, 1 for div
    input wire[7:0] ex_opcode,
    input wire[1:0] ex_opinfo,
    input wire[7:0] ex_tag,
    input wire[1:0] ex_priv,
    input wire[`XLEN-1:0] ex_pc,
// Ouput
    output reg[63:0] d_result,
    output reg d_valid,
    output reg[7:0] d_tag,
    output reg[1:0] d_priv,
    output reg[`XLEN-1:0] d_pc,
    output reg stallreq,
// Divider input & output
    input wire[63:0] q, // quotient
    input wire[63:0] r, // remainder
    input wire div_valid,
    input wire div_idle,
    output reg[63:0] dividend,
    output reg[63:0] divisor,
    output reg div_start
    );
    
    reg[63:0] d_result_next;
    reg d_valid_next;
    reg[7:0] d_tag_next;
    reg[1:0] d_priv_next;
    reg[`XLEN-1:0] d_pc_next;
    wire div_by_zero;
    wire overflow;
    wire[127:0] mul_result;
    reg[63:0] div_result;
    wire[63:0] data1_64_comp;
    wire[63:0] data2_64_comp;
    wire[31:0] data1_32_comp;
    wire[31:0] data2_32_comp;
    reg q_64_sign;
    reg r_64_sign;
    reg q_32_sign;
    reg r_32_sign;
    reg q_64_sign_next;
    reg r_64_sign_next;
    reg q_32_sign_next;
    reg r_32_sign_next;
    reg[7:0] ex_opcode_reg;
    reg[1:0] ex_opinfo_reg;
    reg[7:0] ex_tag_reg;
    reg[1:0] ex_priv_reg;
    reg[`XLEN-1:0] ex_pc_reg;
    reg[7:0] ex_opcode_reg_next;
    reg[1:0] ex_opinfo_reg_next;
    reg[7:0] ex_tag_reg_next;
    reg[1:0] ex_priv_reg_next;
    reg[`XLEN-1:0] ex_pc_reg_next;
    assign div_by_zero = ex_data2[63:0] == 64'b0;
    assign overflow = (ex_opinfo == `Sign32 && ex_data1[31] && ~|ex_data1[30:0] && &ex_data2[31:0]) || (ex_opinfo == `Sign64 && ex_data1[63] && ~|ex_data1[62:0] && &ex_data2[63:0]);
    assign mul_result = ex_data1 + ex_data2;
    assign data1_64_comp = ~ex_data1[63:0] + 64'b1;
    assign data2_64_comp = ~ex_data2[63:0] + 64'b1;
    assign data1_32_comp = ~ex_data1[31:0] + 32'b1;
    assign data2_32_comp = ~ex_data2[31:0] + 32'b1;
	 
    always @ (*) begin
        case (ex_opinfo)
            `Sign32: begin
                dividend = ex_data1[31] ? {32'b0, data1_32_comp[31:0]} : {32'b0, ex_data1[31:0]};
                divisor = ex_data2[31] ? {32'b0, data2_32_comp[31:0]} : {32'b0, ex_data2[31:0]};
            end
            `Sign64: begin
                dividend = ex_data1[63] ? data1_64_comp : ex_data1[63:0];
                divisor = ex_data2[63] ? data2_64_comp : ex_data2[63:0];
            end
            `Unsign32: begin
                dividend = {32'b0, ex_data1[31:0]};
                divisor = {32'b0, ex_data2[31:0]};
            end
            default: begin // `Unsign64
                dividend = ex_data1[63:0];
                divisor = ex_data2[63:0];
            end
        endcase
    end
	 
    always @ (*) begin
        if (div_valid)
            if (ex_opcode_reg == `Mcop_DIV)
                if (ex_opinfo_reg == `Sign32)
                    div_result = q_32_sign ? ~q + 1'b1 : q;
                else if (ex_opinfo_reg == `Sign64)
                    div_result = q_64_sign ? ~q + 1'b1 : q;
                else if (ex_opinfo_reg == `Unsign32)
                    div_result = {{32{q[31]}}, q[31:0]};
                else
                    div_result = q;
            else
                if (ex_opinfo_reg == `Sign32)
                    div_result = r_32_sign ? ~r + 1'b1 : r;
                else if (ex_opinfo_reg == `Sign64)
                    div_result = r_64_sign ? ~r + 1'b1 : r;
                else if (ex_opinfo_reg == `Unsign32)
                    div_result = {{32{r[31]}}, r[31:0]};
                else
                    div_result = r;
        else if (div_by_zero)
            if (ex_opcode == `Mcop_DIV)
                div_result = {64{1'b1}};
            else if (ex_opinfo == `Sign32 || ex_opinfo == `Unsign32)
                div_result = {{32{ex_data1[31]}}, ex_data1[31:0]};
            else
                div_result = ex_data1[63:0];
        else if (overflow)
            if (ex_opcode == `Mcop_DIV)
                if (ex_opinfo == `Sign32)
                    div_result = {{33{1'b1}}, 31'b0};
                else
                    div_result = {1'b1, 63'b0};
                else
                    div_result = 64'b0;
        else
            div_result = 64'b0;
    end
    
    always @ (*) begin
        if (flush) begin
            d_result_next = 64'b0;
            d_valid_next = 1'b0;
            d_tag_next = 8'b0;
            d_priv_next = 2'b0;
            d_pc_next = `XLEN'b0;
            div_start = 1'b0;
            stallreq = 1'b0;
            q_64_sign_next = 1'b0;
            r_64_sign_next = 1'b0;
            q_32_sign_next = 1'b0;
            r_32_sign_next = 1'b0;
            ex_opcode_reg_next = 8'b0;
            ex_opinfo_reg_next = 2'b0;
            ex_tag_reg_next = 8'b0;
            ex_priv_reg_next = 2'b0;
            ex_pc_reg_next = `XLEN'b0;
        end
        else if (stall) begin
            d_result_next = d_result;
            d_valid_next = d_valid;
            d_tag_next = d_tag;
            d_priv_next = d_priv;
            d_pc_next = d_pc;
            div_start = 1'b0;
            stallreq = 1'b0;
            q_64_sign_next = q_64_sign;
            r_64_sign_next = r_64_sign;
            q_32_sign_next = q_32_sign;
            r_32_sign_next = r_32_sign;
            ex_opcode_reg_next = ex_opcode_reg;
            ex_opinfo_reg_next = ex_opinfo_reg;
            ex_tag_reg_next = ex_tag_reg;
            ex_priv_reg_next = ex_priv_reg;
            ex_pc_reg_next = ex_pc_reg;
        end
        else if (div_valid) begin
            d_result_next = div_result;
            d_valid_next = 1'b1;
            d_tag_next = ex_tag_reg;
            d_priv_next = ex_priv_reg;
            d_pc_next = ex_pc_reg;
            div_start = 1'b0;
            stallreq = 1'b0;
            q_64_sign_next = q_64_sign;
            r_64_sign_next = r_64_sign;
            q_32_sign_next = q_32_sign;
            r_32_sign_next = r_32_sign;
            ex_opcode_reg_next = ex_opcode_reg;
            ex_opinfo_reg_next = ex_opinfo_reg;
            ex_tag_reg_next = ex_tag_reg;
            ex_priv_reg_next = ex_priv_reg;
            ex_pc_reg_next = ex_pc_reg;
        end
        else if (ex_div & (div_by_zero | overflow) & ~div_valid & div_idle) begin
            d_result_next = div_result;
            d_valid_next = 1'b1;
            d_tag_next = ex_tag;
            d_priv_next = ex_priv;
            d_pc_next = ex_pc;
            div_start = 1'b0;
            stallreq = 1'b0;
            q_64_sign_next = q_64_sign;
            r_64_sign_next = r_64_sign;
            q_32_sign_next = q_32_sign;
            r_32_sign_next = r_32_sign;
            ex_opcode_reg_next = ex_opcode_reg;
            ex_opinfo_reg_next = ex_opinfo_reg;
            ex_tag_reg_next = ex_tag;
            ex_priv_reg_next = ex_priv;
            ex_pc_reg_next = ex_pc;
        end
        else if (ex_div & ~div_valid & div_idle) begin
            d_result_next = 64'b0;
            d_valid_next = 1'b0;
            d_tag_next = 8'b0;
            d_priv_next = 2'b0;
            d_pc_next = `XLEN'b0;
            div_start = 1'b1;
            stallreq = 1'b1;
            q_64_sign_next = ex_data1[63] ^ ex_data2[63];
            r_64_sign_next = ex_data1[63];
            q_32_sign_next = ex_data1[31] ^ ex_data2[31];
            r_32_sign_next = ex_data1[31];
            ex_opcode_reg_next = ex_opcode;
            ex_opinfo_reg_next = ex_opinfo;
            ex_tag_reg_next = ex_tag;
            ex_priv_reg_next = ex_priv;
            ex_pc_reg_next = ex_pc;
        end
        else if (~div_valid & ~div_idle) begin
            d_result_next = 64'b0;
            d_valid_next = 1'b0;
            d_tag_next = 8'b0;
            d_priv_next = 2'b0;
            d_pc_next = `XLEN'b0;
            div_start = 1'b0;
            stallreq = 1'b1;
            q_64_sign_next = q_64_sign;
            r_64_sign_next = r_64_sign;
            q_32_sign_next = q_32_sign;
            r_32_sign_next = r_32_sign;
            ex_opcode_reg_next = ex_opcode_reg;
            ex_opinfo_reg_next = ex_opinfo_reg;
            ex_tag_reg_next = ex_tag_reg;
            ex_priv_reg_next = ex_priv_reg;
            ex_pc_reg_next = ex_pc_reg;
        end
        else begin
            case (ex_opcode)
                `Mcop_MUL:
                    if (ex_opinfo == `Sign32) begin
                        d_result_next = {{32{mul_result[31]}}, mul_result[31:0]};
                        d_valid_next = 1'b1;
                    end
                    else begin
                        d_result_next = mul_result[63:0];
                        d_valid_next = 1'b1;
                    end
                `Mcop_MULH, `Mcop_MULHS: begin
                    d_result_next = mul_result[127:64];
                    d_valid_next = 1'b1;
                end
                default: begin
                    d_result_next = 64'b0;
                    d_valid_next = 1'b0;
                end
            endcase
            d_tag_next = ex_tag;
            d_priv_next = ex_priv;
            d_pc_next = ex_pc;
            div_start = 1'b0;
            stallreq = 1'b0;
            q_64_sign_next = q_64_sign;
            r_64_sign_next = r_64_sign;
            q_32_sign_next = q_32_sign;
            r_32_sign_next = r_32_sign;
            ex_opcode_reg_next = ex_opcode;
            ex_opinfo_reg_next = ex_opinfo;
            ex_tag_reg_next = ex_tag;
            ex_priv_reg_next = ex_priv;
            ex_pc_reg_next = ex_pc;
        end
    end

    always @ (posedge clk or posedge arst) begin
        if (arst) begin
            d_result <= 64'b0;
            d_valid <= 1'b0;
            d_tag <= 8'b0;
            d_priv <= 2'b0;
            d_pc <= `XLEN'b0;
            q_64_sign <= 1'b0;
            r_64_sign <= 1'b0;
            q_32_sign <= 1'b0;
            r_32_sign <= 1'b0;
            ex_opcode_reg <= 8'b0;
            ex_opinfo_reg <= 2'b0;
            ex_tag_reg = 8'b0;
            ex_priv_reg = 2'b0;
            ex_pc_reg = `XLEN'b0;
        end
        else begin
            d_result <= d_result_next;
            d_valid <= d_valid_next;
            d_tag <= d_tag_next;
            d_priv <= d_priv_next;
            d_pc <= d_pc_next;
            q_64_sign <= q_64_sign_next;
            r_64_sign <= r_64_sign_next;
            q_32_sign <= q_32_sign_next;
            r_32_sign <= r_32_sign_next;
            ex_opcode_reg <= ex_opcode_reg_next;
            ex_opinfo_reg <= ex_opinfo_reg_next;
            ex_tag_reg = ex_tag_reg_next;
            ex_priv_reg = ex_priv_reg_next;
            ex_pc_reg = ex_pc_reg_next;
        end
    end

endmodule

